{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fTChud-hXv5e"
      },
      "source": [
        "# Intermediate Modeling: Return of the Widgets\n",
        "\n",
        "In Opti101 we first learned about mathematical optimization through a fictitious problem about producing and distributing widgets. If you would like to have the first notebook using this example from the previous training handy, you can find it [here](https://colab.research.google.com/github/Gurobi/modeling-examples/blob/master/optimization101/Modeling_Session_1/completed_modeling1.ipynb).\n",
        "\n",
        "We are going to expand on this problem, so let's do a quick recap. We make widgets. They are produced in one of **five production facilities** and are then sent to one of **six distribution hubs** to be sold locally. Each distribution center has a demand forecast and each production facility has a min and max number of widgets it can make during this period.\n",
        "\n",
        "Our production and distribution `sets` are:\n",
        "- $P = \\{\\texttt{'Baltimore', 'Cleveland', 'Little Rock', 'Birmingham', 'Charleston'}\\}$ $\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\space\\space \\texttt{production}$\n",
        "- $D = \\{\\texttt{'Columbia', 'Indianapolis', 'Lexington', 'Nashville', 'Richmond', 'St. Louis'}\\} \\quad\\quad\\quad \\texttt{distribution}$\n",
        "\n",
        "Model `parameters`:\n",
        "- $c_{p,d}$: cost to ship a widget from $p$ to $d$, $\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\texttt{transp_cost[p,d]}$\n",
        "- $m_p$: maximum a production facility $p$ can produce, $\\quad\\quad\\quad\\quad\\quad\\quad\\space\\texttt{max_prod[p]}$\n",
        "- $n_d$: demand at distribution hub $d$, $\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\quad\\texttt{n_demand[d]}$\n",
        "\n",
        "If you are running this in Colab or don't have a Gurobi license, quickly installing `gurobipy` will install a limited license."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ZrTC2WIa_4j9",
        "outputId": "8e9b751e-4cc2-4bc3-a9b0-914c65aeef61"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Collecting gurobipy\n",
            "  Downloading gurobipy-10.0.3-cp310-cp310-manylinux2014_x86_64.whl (12.7 MB)\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m12.7/12.7 MB\u001b[0m \u001b[31m58.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hInstalling collected packages: gurobipy\n",
            "Successfully installed gurobipy-10.0.3\n"
          ]
        }
      ],
      "source": [
        "%pip install gurobipy\n",
        "\n",
        "# Import packages\n",
        "import pandas as pd\n",
        "import gurobipy as gp\n",
        "from gurobipy import GRB"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kpWARrwbBUii"
      },
      "outputs": [],
      "source": [
        "# read in transportation cost data\n",
        "path = 'https://raw.githubusercontent.com/Gurobi/modeling-examples/master/optimization101/Modeling_Session_1/'\n",
        "transp_cost = pd.read_csv(path + 'cost.csv')\n",
        "\n",
        "# get production and distribution locations from data frame\n",
        "production = list(transp_cost['production'].unique())\n",
        "distribution = list(transp_cost['distribution'].unique())\n",
        "transp_cost = transp_cost.set_index(['production','distribution']).squeeze()\n",
        "\n",
        "max_prod = pd.Series([180,200,140,80,180], index = production, name = \"max_production\")\n",
        "n_demand = pd.Series([89,95,121,101,116,181], index = distribution, name = \"demand\")\n",
        "# the min prodcution is a fraction of the max\n",
        "frac = 0.75"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hPQwd0Ip_5WL"
      },
      "source": [
        "### The First Model\n",
        "\n",
        "- Our **decision variables** are the amount produced at facility $p$ and shipped to distribution center $d$, denoted $x_{p,d}$\n",
        "- We have **constraints** to ensure:\n",
        "  - Min and max production\n",
        "  - Demand is met\n",
        "- The **objective** is to meet demand at **minimal cost**.\n",
        "\n",
        "As a reminder, here is the original *formulation*:\n",
        "\n",
        "\\begin{align*}\n",
        "{\\rm min} &\\sum_{p,d}c_{p,d}x_{p,d}\\\\\n",
        "{\\rm s.t.}\\\\\n",
        "&\\sum_{d}x_{p,d} \\le m_p, &\\forall p \\in P \\quad &\\texttt{can}\\_\\texttt{produce[p]}\\\\\n",
        "&\\sum_{d}x_{p,d} \\ge a*m_p,&\\forall p \\in P \\quad &\\texttt{must}\\_\\texttt{produce[p]}\\\\\n",
        "&\\sum_{p}x_{p,d} \\ge n_d, &\\forall d \\in D \\quad &\\texttt{meet}\\_\\texttt{demand[d]}\\\\\n",
        "&x_{p,d} \\ge 0,  &\\forall p \\in P, d \\in D\\quad &\\texttt{non-negativity}\\\\\n",
        "\\end{align*}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Si4laKSqnQxg",
        "outputId": "c205df28-86dd-4690-c4d1-da80571d4fa8"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Restricted license - for non-production use only - expires 2024-10-28\n"
          ]
        }
      ],
      "source": [
        "# gurobipy code for thie above formulation\n",
        "m = gp.Model('widgets')\n",
        "\n",
        "# decision vars\n",
        "x = m.addVars(production, distribution, name = 'prod_ship')\n",
        "\n",
        "# constraints\n",
        "can_produce = m.addConstrs((gp.quicksum(x[p,d] for d in distribution) <= max_prod[p] for p in production), name = 'can_produce')\n",
        "must_produce = m.addConstrs((gp.quicksum(x[p,d] for d in distribution) >= frac*max_prod[p] for p in production), name = 'must_produce')\n",
        "meet_demand = m.addConstrs(x.sum('*', d) >= n_demand[d] for d in distribution)\n",
        "\n",
        "#objective\n",
        "m.setObjective(gp.quicksum(transp_cost[p,d]*x[p,d] for p in production for d in distribution), GRB.MINIMIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rgoFETiqNrc2"
      },
      "source": [
        "Solve the optimization problem, and look at the results."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 782
        },
        "id": "WZOYz8OxOFyb",
        "outputId": "6ea3b133-3407-491d-cde7-847b24c91975"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 16 rows, 30 columns and 90 nonzeros\n",
            "Model fingerprint: 0x6eb01f9f\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 1e+00]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [0e+00, 0e+00]\n",
            "  RHS range        [6e+01, 2e+02]\n",
            "Presolve removed 5 rows and 0 columns\n",
            "Presolve time: 0.01s\n",
            "Presolved: 11 rows, 35 columns, 65 nonzeros\n",
            "\n",
            "Iteration    Objective       Primal Inf.    Dual Inf.      Time\n",
            "       0    0.0000000e+00   1.610000e+02   0.000000e+00      0s\n",
            "      15    1.7048900e+03   0.000000e+00   0.000000e+00      0s\n",
            "\n",
            "Solved in 15 iterations and 0.01 seconds (0.00 work units)\n",
            "Optimal objective  1.704890000e+03\n",
            "The original model had a total cost of 1704.89\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "  <div id=\"df-195c039f-3069-441b-8ce9-30ffd5d5c41a\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "      <th>cost</th>\n",
              "      <th>shipment</th>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>production</th>\n",
              "      <th>distribution</th>\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Baltimore</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>5.96</td>\n",
              "      <td>19.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Richmond</th>\n",
              "      <td>1.96</td>\n",
              "      <td>116.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"3\" valign=\"top\">Cleveland</th>\n",
              "      <th>Columbia</th>\n",
              "      <td>2.43</td>\n",
              "      <td>89.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Indianapolis</th>\n",
              "      <td>2.37</td>\n",
              "      <td>95.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Nashville</th>\n",
              "      <td>4.13</td>\n",
              "      <td>2.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Little Rock</th>\n",
              "      <th>St. Louis</th>\n",
              "      <td>2.92</td>\n",
              "      <td>140.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Birmingham</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>1.53</td>\n",
              "      <td>80.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Charleston</th>\n",
              "      <th>Lexington</th>\n",
              "      <td>1.61</td>\n",
              "      <td>121.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.60</td>\n",
              "      <td>41.0</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-195c039f-3069-441b-8ce9-30ffd5d5c41a')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-195c039f-3069-441b-8ce9-30ffd5d5c41a button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-195c039f-3069-441b-8ce9-30ffd5d5c41a');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-db9772a4-2284-4481-bdd7-80e88e8051d1\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-db9772a4-2284-4481-bdd7-80e88e8051d1')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-db9772a4-2284-4481-bdd7-80e88e8051d1 button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ],
            "text/plain": [
              "                          cost  shipment\n",
              "production  distribution                \n",
              "Baltimore   Nashville     5.96      19.0\n",
              "            Richmond      1.96     116.0\n",
              "Cleveland   Columbia      2.43      89.0\n",
              "            Indianapolis  2.37      95.0\n",
              "            Nashville     4.13       2.0\n",
              "Little Rock St. Louis     2.92     140.0\n",
              "Birmingham  Nashville     1.53      80.0\n",
              "Charleston  Lexington     1.61     121.0\n",
              "            St. Louis     4.60      41.0"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "m.optimize()\n",
        "\n",
        "x_values = pd.Series(m.getAttr('X', x), name = \"shipment\", index = transp_cost.index)\n",
        "sol = pd.concat([transp_cost, x_values], axis=1)\n",
        "obj0 = m.getObjective()\n",
        "obj0_value = obj0.getValue()\n",
        "\n",
        "print(f\"The original model had a total cost of {round(obj0_value,2)}\")\n",
        "sol[sol.shipment > 0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jiyCPYlCG9Az"
      },
      "source": [
        "### Fixing the very small shipment value\n",
        "In the Intermediate Modeling recorded presentation, we saw a decision that may not be actually doable in an optimal solution.\n",
        "\n",
        "$x_{Cleveland,Nashville} = 2$, meaning we have a shipment of only two units. It's *possible* that this is something we may need to avoid for various reasons, say for instance the transportation cost per unit is only valid for a minimum amount.\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "addJBPMNHEZL"
      },
      "source": [
        "First, let's work through some of the logic in order to address this. The statement we want to model in words:\n",
        "\n",
        "\"If widgets are shipped from $p$ to $d$, then it must be at least $C$ units.\"\n",
        "\n",
        "Let's replace some of the words with our decision variables and inequalities."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oSK9Ezc_Ich8"
      },
      "source": [
        "##### If _________, then _________."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7ZFS4jzCKBd1"
      },
      "source": [
        "If $x_{p,d} > 0$, then $x_{p,d} \\ge C$."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cF8C75m1Idh3"
      },
      "source": [
        "##### Let's rewrite the original statement using the \"IF-THEN to OR\" equivalence mentioned in the previous session.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o76FyR4aKQyV"
      },
      "source": [
        "$x_{p,d} \\le 0$ or $x_{p,d} \\ge C$.\n",
        "\n",
        "Now let's replace the words again with our notation.\n",
        "$$\n",
        "x_{p,d} \\le 0 \\quad \\texttt{or} \\quad x_{p,d} \\ge C\n",
        "$$\n",
        "\n",
        "We know that $x_{p,d}$ is already non-negative, so how can we strenghten what we have above?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MKXWkw8TIzDV"
      },
      "source": [
        "##### Now how do we actually model this?\n",
        "We can do this in a few different ways:\n",
        "- Big M\n",
        "- Gurobi's indicator constraints\n",
        "- Using a different variable type in Gurobi!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o2-SRYZph2BE"
      },
      "source": [
        "#### Using Big M Constraints\n",
        "Let's first look at this in a general way, so we won't use our problem's subscripts initially. We could also take the more formulaic approach outlined in the previous session to build this, but there are times (with a little experience) where these can be built more intuitively. We will work through modeling\n",
        "$$\n",
        "x \\le 0 \\quad \\texttt{OR} \\quad x \\ge C\n",
        "$$\n",
        "\n",
        "Since we have two clauses in the `OR` statement, let's use two [*auxiliary* variables](https://support.gurobi.com/hc/en-us/community/posts/10621090066321-What-is-exactly-auxiliary-variables-in-the-opt-model-), $z_1$ and $z_2$.\n",
        "\n",
        "The inequality to the left of the `OR` is $x \\le 0$. Remember the general point is to enforce a desired constraint when the binary variable takes a specific value, and then implement no restrictions when it takes the opposite value."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HmLXki8LNBFp"
      },
      "source": [
        "##### Clause 1 of `OR` statement:\n",
        "We can write this first clause using a simple big M constraint"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SQJLyE8wHBn7"
      },
      "source": [
        "$$\n",
        "x \\le M_1 \\times z_1\n",
        "$$\n",
        "\n",
        "If $z_1 = 0$, then $x \\le 0$. If $z_1 = 1$, then $x \\le M_1$. Assuming $M_1$ is large enough, then there is no resrtiction on $x$."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Kxjb1kiOM5A9"
      },
      "source": [
        "##### For the right side of the `OR` statement, we have $x \\ge C$. Again, in one case of the auxiliary variable we want the desired inequality, and the other case to put no restrictions on our variable.\n",
        "\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EYXzWzruJrAf"
      },
      "source": [
        "$$\n",
        "x + M_2 \\times z_2 \\ge C\n",
        "$$\n",
        "\n",
        "If $z_2 = 0$, then $x \\ge C$. If $z_2 = 1$, then (as long as $M_2 \\ge C$) there is no restriction on $x$."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Nl7_TcleJrPE"
      },
      "source": [
        "##### Next, we need to see how the auxiliary variables need to work together in this case. Since this is an `OR` statement, one of these needs to happen, so this gives us:"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NiHRdt7TJ3zj"
      },
      "source": [
        "$$\n",
        "z_1 + z_2 \\ge 1\n",
        "$$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "J8JBY0XVJ4FO"
      },
      "source": [
        "##### And can both of these happen together (i.e. can $z_1 = z_2 = 1$)? Nope. So we can strengthen this to:\n",
        "$$\n",
        "\\begin{align*}\n",
        "z_1 + z_2 &= 1\\\\\n",
        "z_1 &= 1-z_2\n",
        "\\end{align*}\n",
        "$$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MKZQ3WZbC1WL"
      },
      "source": [
        "##### Let's substitute this into the above inequality and since we have only one auxiliary variable in this simplification we can just call it $z$.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fL8_ARH-Dfud"
      },
      "source": [
        "$$\n",
        "\\begin{align*}\n",
        "&x \\le M_1 \\times (1-z)\\\\\n",
        "&x + M_2 \\times z \\ge C\n",
        "\\end{align*}\n",
        "$$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MwWX7f-hC2gr"
      },
      "source": [
        "##### OK - now back to our problem. We need to define the new *auxiliary* binary variable for each **production facility** and **distribution center**, so we will call it $z_{p,d}$.\n",
        "\n",
        "This gives us:\n",
        "$$\n",
        "\\begin{align*}\n",
        "&x_{p,d} \\le M^1_{p,d} \\times (1-z)\\\\\n",
        "&x + M^2_{p,d} \\times z \\ge C\n",
        "\\end{align*}\n",
        "$$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wa3rkPPyBNNh"
      },
      "source": [
        "##### Lastly, before we get to actually implementing this, let's figure out some good choices for the $M$s above.\n",
        "Looking at $x_{p,d} \\le M^1_{p,d}$, can we think what else is an *upper-bound* on $x_{p,d}$?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f1I-lJDfA-V9"
      },
      "source": [
        "A good choice would be the maximum amount that a production facility can produce, $m_p = \\texttt{max_prod[p]}$ (Don't get $m$ and $M$ confused!). $C$ is a clear choice for each $M^2_{p,d}$. Alright, let's code it by adding the auxiliary variables first.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UqtiL8ZSBba6"
      },
      "source": [
        "##### Next, let's add the constraints developed above."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "u0CNXnanuVqu"
      },
      "outputs": [],
      "source": [
        "z = m.addVars(production, distribution, vtype=GRB.BINARY, name = 'min_distribution')\n",
        "\n",
        "min_ship1 = m.addConstrs(    ?????????     for p in production for d in distribution)\n",
        "m.update()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rChMOWjTvW-P"
      },
      "source": [
        "Ah, we still need to decide on a value for $C$. We'll set it to 30 for now."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8KUB_0_LvPwv"
      },
      "outputs": [],
      "source": [
        "C = 30\n",
        "min_ship2 = m.addConstrs(    ?????????   for p in production for d in distribution)\n",
        "m.update()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4ns_8hC7wIVY"
      },
      "source": [
        "Solve and look at the new solution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "CEdG4St7wMP4",
        "outputId": "ad732500-517d-47fe-a3b1-fb85ffbe75a8"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 76 rows, 60 columns and 210 nonzeros\n",
            "Variable types: 30 continuous, 30 integer (30 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 2e+02]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [3e+01, 2e+02]\n",
            "Found heuristic solution: objective 2825.4400000\n",
            "Presolve time: 0.00s\n",
            "Presolved: 76 rows, 60 columns, 210 nonzeros\n",
            "Variable types: 30 continuous, 30 integer (30 binary)\n",
            "\n",
            "Root relaxation: objective 1.704890e+03, 34 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "     0     0 1704.89000    0    2 2825.44000 1704.89000  39.7%     -    0s\n",
            "H    0     0                    1748.4200000 1704.89000  2.49%     -    0s\n",
            "     0     0 1734.12000    0    1 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1734.12000    0    2 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1734.12000    0    1 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1734.12000    0    1 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1741.29463    0    3 1748.42000 1741.29463  0.41%     -    0s\n",
            "     0     0     cutoff    0      1748.42000 1748.42000  0.00%     -    0s\n",
            "\n",
            "Cutting planes:\n",
            "  Gomory: 1\n",
            "  Cover: 3\n",
            "  Implied bound: 3\n",
            "  MIR: 5\n",
            "  Flow cover: 2\n",
            "  Relax-and-lift: 1\n",
            "\n",
            "Explored 1 nodes (83 simplex iterations) in 0.04 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 2: 1748.42 2825.44 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 1.748420000000e+03, best bound 1.748420000000e+03, gap 0.0000%\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "  <div id=\"df-8819bd12-22f0-4a64-8773-42bba35bb77e\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "      <th>cost</th>\n",
              "      <th>shipment</th>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>production</th>\n",
              "      <th>distribution</th>\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Baltimore</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>5.96</td>\n",
              "      <td>30.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Richmond</th>\n",
              "      <td>1.96</td>\n",
              "      <td>116.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Cleveland</th>\n",
              "      <th>Columbia</th>\n",
              "      <td>2.43</td>\n",
              "      <td>89.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Indianapolis</th>\n",
              "      <td>2.37</td>\n",
              "      <td>95.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Little Rock</th>\n",
              "      <th>St. Louis</th>\n",
              "      <td>2.92</td>\n",
              "      <td>140.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Birmingham</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>1.53</td>\n",
              "      <td>71.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Charleston</th>\n",
              "      <th>Lexington</th>\n",
              "      <td>1.61</td>\n",
              "      <td>121.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.60</td>\n",
              "      <td>41.0</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-8819bd12-22f0-4a64-8773-42bba35bb77e')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-8819bd12-22f0-4a64-8773-42bba35bb77e button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-8819bd12-22f0-4a64-8773-42bba35bb77e');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-107d72e7-cd2b-47e9-8043-54a4bbeff441\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-107d72e7-cd2b-47e9-8043-54a4bbeff441')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-107d72e7-cd2b-47e9-8043-54a4bbeff441 button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ],
            "text/plain": [
              "                          cost  shipment\n",
              "production  distribution                \n",
              "Baltimore   Nashville     5.96      30.0\n",
              "            Richmond      1.96     116.0\n",
              "Cleveland   Columbia      2.43      89.0\n",
              "            Indianapolis  2.37      95.0\n",
              "Little Rock St. Louis     2.92     140.0\n",
              "Birmingham  Nashville     1.53      71.0\n",
              "Charleston  Lexington     1.61     121.0\n",
              "            St. Louis     4.60      41.0"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "m.optimize()\n",
        "\n",
        "x_values = pd.Series(m.getAttr('X', x), name = \"shipment\", index = transp_cost.index)\n",
        "sol = pd.concat([transp_cost, x_values], axis=1)\n",
        "obj1 = m.getObjective()\n",
        "obj1_value = obj1.getValue()\n",
        "sol[sol.shipment > 0]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "p9Roh3qpwWdT",
        "outputId": "093238fd-d0b8-4df4-eb9e-d010dfb84723"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The updated model had a total cost of 1748.42.\n",
            "The additional cost is 43.53.\n"
          ]
        }
      ],
      "source": [
        "print(f\"The updated model had a total cost of {round(obj1_value,2)}.\")\n",
        "print(f\"The additional cost is {round(obj1_value - obj0_value,2)}.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "I5PyDdGu1QDm"
      },
      "source": [
        "#### Using Gurobi's Indicator Constraint\n",
        "That was A LOT of work, you must be thinking \"Since this is formulaic, is there a better way?\" Well the answer is **yes**. This is where [indicator constriaints](https://www.gurobi.com/documentation/current/refman/py_model_agc_indicator.html) can be very useful.\n",
        "\n",
        "These allow us to be more direct in modeling `IF-THEN` statements. These constraints work by utilizing a single `binary` decision variable as the **IF**, with another constraint as the **THEN**.\n",
        "\n",
        "This boils down to \"If the binary variable equals 1, then enforce the constraint. If it's 0, then don't.\"\n",
        "\n",
        "Because of this, we still need the *auxiliary* variables $z$, but from there it's a bit simpler."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dkJ60I4z4GqE"
      },
      "outputs": [],
      "source": [
        "# remove the big M constraints and variable\n",
        "m.remove([min_ship1, min_ship2])\n",
        "C = 30\n",
        "# overloaded form\n",
        "zis1 = m.addConstrs((((z[p,d] == 1 ) >> (  ?????  )) for p in production for d in distribution), name = \"zis1\")\n",
        "zis0 = m.addConstrs((((   ??????   ) >> (  ?????  )) for p in production for d in distribution), name = \"zis0\")\n",
        "m.update()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "CGstPy1G7vFE",
        "outputId": "08598c83-1860-439c-e289-d970cac81b6a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 16 rows, 60 columns and 90 nonzeros\n",
            "Model fingerprint: 0x6accf263\n",
            "Model has 60 general constraints\n",
            "Variable types: 30 continuous, 30 integer (30 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 1e+00]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [6e+01, 2e+02]\n",
            "  GenCon rhs range [3e+01, 3e+01]\n",
            "  GenCon coe range [1e+00, 1e+00]\n",
            "\n",
            "MIP start from previous solve did not produce a new incumbent solution\n",
            "\n",
            "Presolve added 60 rows and 0 columns\n",
            "Presolve time: 0.00s\n",
            "Presolved: 76 rows, 60 columns, 210 nonzeros\n",
            "Variable types: 30 continuous, 30 integer (30 binary)\n",
            "Found heuristic solution: objective 2998.0600000\n",
            "\n",
            "Root relaxation: objective 1.704890e+03, 32 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "     0     0 1704.89000    0    2 2998.06000 1704.89000  43.1%     -    0s\n",
            "H    0     0                    1748.4200000 1704.89000  2.49%     -    0s\n",
            "     0     0 1734.12000    0    1 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1734.12000    0    2 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1737.01625    0    1 1748.42000 1737.01625  0.65%     -    0s\n",
            "\n",
            "Cutting planes:\n",
            "  Cover: 1\n",
            "  MIR: 2\n",
            "  Flow cover: 2\n",
            "\n",
            "Explored 1 nodes (75 simplex iterations) in 0.04 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 2: 1748.42 2998.06 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 1.748420000000e+03, best bound 1.748420000000e+03, gap 0.0000%\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "  <div id=\"df-c127f75c-5494-48a1-a9f3-a94010b451a3\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "      <th>cost</th>\n",
              "      <th>shipment</th>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>production</th>\n",
              "      <th>distribution</th>\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Baltimore</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>5.96</td>\n",
              "      <td>30.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Richmond</th>\n",
              "      <td>1.96</td>\n",
              "      <td>116.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Cleveland</th>\n",
              "      <th>Columbia</th>\n",
              "      <td>2.43</td>\n",
              "      <td>89.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Indianapolis</th>\n",
              "      <td>2.37</td>\n",
              "      <td>95.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Little Rock</th>\n",
              "      <th>St. Louis</th>\n",
              "      <td>2.92</td>\n",
              "      <td>140.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Birmingham</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>1.53</td>\n",
              "      <td>71.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Charleston</th>\n",
              "      <th>Lexington</th>\n",
              "      <td>1.61</td>\n",
              "      <td>121.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.60</td>\n",
              "      <td>41.0</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-c127f75c-5494-48a1-a9f3-a94010b451a3')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-c127f75c-5494-48a1-a9f3-a94010b451a3 button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-c127f75c-5494-48a1-a9f3-a94010b451a3');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-6f51f591-25bf-4eae-aeae-3841e67638b5\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-6f51f591-25bf-4eae-aeae-3841e67638b5')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-6f51f591-25bf-4eae-aeae-3841e67638b5 button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ],
            "text/plain": [
              "                          cost  shipment\n",
              "production  distribution                \n",
              "Baltimore   Nashville     5.96      30.0\n",
              "            Richmond      1.96     116.0\n",
              "Cleveland   Columbia      2.43      89.0\n",
              "            Indianapolis  2.37      95.0\n",
              "Little Rock St. Louis     2.92     140.0\n",
              "Birmingham  Nashville     1.53      71.0\n",
              "Charleston  Lexington     1.61     121.0\n",
              "            St. Louis     4.60      41.0"
            ]
          },
          "execution_count": 11,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "m.optimize()\n",
        "\n",
        "x_values = pd.Series(m.getAttr('X', x), name = \"shipment\", index = transp_cost.index)\n",
        "sol = pd.concat([transp_cost, x_values], axis=1)\n",
        "sol[sol.shipment > 0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SKRgM-NB-wOr"
      },
      "source": [
        "This solution looks awfully familiar...and was pretty easy to model! Can it get easier?\n",
        "\n",
        "#### Using Semi-Continuous Variables\n",
        "This type of constraint, where we want a decision variable $x$ such that\n",
        "$$\n",
        "x=0 \\quad \\texttt{or} \\quad l \\le x \\le u\n",
        "$$\n",
        "we can define $x_{p,d}$ as a `semi-continuous` decision variable and specify the corresponding *lower* and *upper* bounds"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Rh2IC2l3C0Zv"
      },
      "outputs": [],
      "source": [
        "m.remove([zis1, zis0])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PmPssvCmC_b1"
      },
      "source": [
        "Let's rewrite the whole model using the `semi-continuous` variable and solve one more time."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "pG76zVKFDDEG",
        "outputId": "df228ed5-671f-4115-9926-0cfd7d3962ac"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 16 rows, 30 columns and 90 nonzeros\n",
            "Model fingerprint: 0x27f83804\n",
            "Variable types: 0 continuous, 0 integer (0 binary)\n",
            "Semi-Variable types: 30 continuous, 0 integer\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 1e+00]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [3e+01, 3e+01]\n",
            "  RHS range        [6e+01, 2e+02]\n",
            "Presolve time: 0.00s\n",
            "Presolved: 76 rows, 60 columns, 210 nonzeros\n",
            "Variable types: 30 continuous, 30 integer (30 binary)\n",
            "\n",
            "Root relaxation: objective 1.704890e+03, 21 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "     0     0 1704.89000    0    2          - 1704.89000      -     -    0s\n",
            "H    0     0                    1748.4200000 1704.89000  2.49%     -    0s\n",
            "     0     0 1734.12000    0    1 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1734.12000    0    2 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1734.12000    0    1 1748.42000 1734.12000  0.82%     -    0s\n",
            "     0     0 1739.02000    0    1 1748.42000 1739.02000  0.54%     -    0s\n",
            "     0     0 infeasible    0      1748.42000 1748.42000  0.00%     -    0s\n",
            "\n",
            "Cutting planes:\n",
            "  Implied bound: 1\n",
            "  MIR: 2\n",
            "  Flow cover: 4\n",
            "  Relax-and-lift: 1\n",
            "\n",
            "Explored 1 nodes (74 simplex iterations) in 0.03 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 1: 1748.42 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 1.748420000000e+03, best bound 1.748420000000e+03, gap 0.0000%\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "  <div id=\"df-53cc31c2-cabe-4fdf-a064-5bc6e5e4c4aa\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "      <th>cost</th>\n",
              "      <th>shipment</th>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>production</th>\n",
              "      <th>distribution</th>\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Baltimore</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>5.96</td>\n",
              "      <td>30.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Richmond</th>\n",
              "      <td>1.96</td>\n",
              "      <td>116.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Cleveland</th>\n",
              "      <th>Columbia</th>\n",
              "      <td>2.43</td>\n",
              "      <td>89.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Indianapolis</th>\n",
              "      <td>2.37</td>\n",
              "      <td>95.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Little Rock</th>\n",
              "      <th>St. Louis</th>\n",
              "      <td>2.92</td>\n",
              "      <td>140.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Birmingham</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>1.53</td>\n",
              "      <td>71.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Charleston</th>\n",
              "      <th>Lexington</th>\n",
              "      <td>1.61</td>\n",
              "      <td>121.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.60</td>\n",
              "      <td>41.0</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-53cc31c2-cabe-4fdf-a064-5bc6e5e4c4aa')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-53cc31c2-cabe-4fdf-a064-5bc6e5e4c4aa button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-53cc31c2-cabe-4fdf-a064-5bc6e5e4c4aa');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-cdd33f2c-0bf7-43a0-8dee-97b51907b76d\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-cdd33f2c-0bf7-43a0-8dee-97b51907b76d')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-cdd33f2c-0bf7-43a0-8dee-97b51907b76d button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ],
            "text/plain": [
              "                          cost  shipment\n",
              "production  distribution                \n",
              "Baltimore   Nashville     5.96      30.0\n",
              "            Richmond      1.96     116.0\n",
              "Cleveland   Columbia      2.43      89.0\n",
              "            Indianapolis  2.37      95.0\n",
              "Little Rock St. Louis     2.92     140.0\n",
              "Birmingham  Nashville     1.53      71.0\n",
              "Charleston  Lexington     1.61     121.0\n",
              "            St. Louis     4.60      41.0"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# decision vars\n",
        "m = gp.Model('widgets')\n",
        "\n",
        "# decision vars\n",
        "x = m.addVars(production, distribution, vtype=GRB.SEMICONT, lb = C, name = 'prod_ship')\n",
        "\n",
        "# constraints\n",
        "can_produce = m.addConstrs((gp.quicksum(x[p,d] for d in distribution) <= max_prod[p] for p in production), name = 'can_produce')\n",
        "must_produce = m.addConstrs((gp.quicksum(x[p,d] for d in distribution) >= frac*max_prod[p] for p in production), name = 'must_produce')\n",
        "meet_demand = m.addConstrs(x.sum('*', d) >= n_demand[d] for d in distribution)\n",
        "\n",
        "# objective\n",
        "m.setObjective(gp.quicksum(transp_cost[p,d]*x[p,d] for p in production for d in distribution), GRB.MINIMIZE)\n",
        "m.optimize()\n",
        "\n",
        "# extract and show solution\n",
        "x_values = pd.Series(m.getAttr('X', x), name = \"shipment\", index = transp_cost.index)\n",
        "sol = pd.concat([transp_cost, x_values], axis=1)\n",
        "sol[sol.shipment > 0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2cNcBDSyXue1"
      },
      "source": [
        "## Constraining production facilities\n",
        "\n",
        "For this part of the notebook, we will use different numbers for the maximum amount a facility can produce."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "S2JhELuMEzs8"
      },
      "outputs": [],
      "source": [
        "max_prod2 = pd.Series([210,225,140,130,220], index = production, name = \"max_production\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TTOd9-qeLXLg"
      },
      "source": [
        "We also have to make one more substantial change to the model now that all facilities are not required to be open. The $\\space\\texttt{must}\\_\\texttt{produce[p]}$ contraint needs to be removed since this set of constraints, as written, will make all production facilities open. Let's define a new model, call it `m2`, and code up the parts we want to port over from the first model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jwIpY2ueNLXf"
      },
      "outputs": [],
      "source": [
        "m2 = gp.Model('widgets2')\n",
        "\n",
        "# decision vars\n",
        "x = m2.addVars(production, distribution, name = 'prod_ship')\n",
        "\n",
        "# constraints\n",
        "can_produce = m2.addConstrs((gp.quicksum(x[p,d] for d in distribution) <= max_prod2[p] for p in production), name = 'can_produce')\n",
        "meet_demand = m2.addConstrs(x.sum('*', d) >= n_demand[d] for d in distribution)\n",
        "\n",
        "# define total cost for quick access later\n",
        "total_cost = gp.quicksum(transp_cost[p,d]*x[p,d] for p in production for d in distribution)\n",
        "\n",
        "# objective\n",
        "m2.setObjective(total_cost, GRB.MINIMIZE)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WLwXJlQgTQn-"
      },
      "source": [
        "Let's run the optimization to establish a baseline before using the new decision variables."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 696
        },
        "id": "1vsc8xdvPo1C",
        "outputId": "eb2d2043-a832-45c8-9d6b-3bce219bd4c1"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 11 rows, 30 columns and 60 nonzeros\n",
            "Model fingerprint: 0xa467002a\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 1e+00]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [0e+00, 0e+00]\n",
            "  RHS range        [9e+01, 2e+02]\n",
            "Presolve time: 0.01s\n",
            "Presolved: 11 rows, 30 columns, 60 nonzeros\n",
            "\n",
            "Iteration    Objective       Primal Inf.    Dual Inf.      Time\n",
            "       0    0.0000000e+00   7.030000e+02   0.000000e+00      0s\n",
            "       8    1.5984100e+03   0.000000e+00   0.000000e+00      0s\n",
            "\n",
            "Solved in 8 iterations and 0.01 seconds (0.00 work units)\n",
            "Optimal objective  1.598410000e+03\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "  <div id=\"df-33d3c594-3e1d-4ccf-8dae-5f75761bb7a7\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "      <th>cost</th>\n",
              "      <th>shipment</th>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>production</th>\n",
              "      <th>distribution</th>\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>Baltimore</th>\n",
              "      <th>Richmond</th>\n",
              "      <td>1.96</td>\n",
              "      <td>116.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Cleveland</th>\n",
              "      <th>Columbia</th>\n",
              "      <td>2.43</td>\n",
              "      <td>89.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Indianapolis</th>\n",
              "      <td>2.37</td>\n",
              "      <td>95.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Little Rock</th>\n",
              "      <th>St. Louis</th>\n",
              "      <td>2.92</td>\n",
              "      <td>140.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Birmingham</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>1.53</td>\n",
              "      <td>101.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.01</td>\n",
              "      <td>29.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Charleston</th>\n",
              "      <th>Lexington</th>\n",
              "      <td>1.61</td>\n",
              "      <td>121.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.60</td>\n",
              "      <td>12.0</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-33d3c594-3e1d-4ccf-8dae-5f75761bb7a7')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-33d3c594-3e1d-4ccf-8dae-5f75761bb7a7 button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-33d3c594-3e1d-4ccf-8dae-5f75761bb7a7');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-16dcc743-c5ba-4740-a854-8775ff0c9541\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-16dcc743-c5ba-4740-a854-8775ff0c9541')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-16dcc743-c5ba-4740-a854-8775ff0c9541 button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ],
            "text/plain": [
              "                          cost  shipment\n",
              "production  distribution                \n",
              "Baltimore   Richmond      1.96     116.0\n",
              "Cleveland   Columbia      2.43      89.0\n",
              "            Indianapolis  2.37      95.0\n",
              "Little Rock St. Louis     2.92     140.0\n",
              "Birmingham  Nashville     1.53     101.0\n",
              "            St. Louis     4.01      29.0\n",
              "Charleston  Lexington     1.61     121.0\n",
              "            St. Louis     4.60      12.0"
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "m2.optimize()\n",
        "x_values = pd.Series(m2.getAttr('X', x), name = \"shipment\", index = transp_cost.index)\n",
        "sol2 = pd.concat([transp_cost, x_values], axis=1)\n",
        "sol2[sol2.shipment > 0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "250i0BfoOHgc"
      },
      "source": [
        "Now add the new decision variables to the model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "BQWp5hdDOIHA"
      },
      "outputs": [],
      "source": [
        "y = m2.addVars(production, vtype= ?????, name = 'prod_on')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ogTrbLekOemr"
      },
      "source": [
        "As discussed in the previous session, we need to be a little careful when linking $x_{p,d}$ and $y_p$ decision varibles. Let's use the `Big-M` approach. Using indicator constraints is part of the exercises."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NPA-Js0TPdyI"
      },
      "outputs": [],
      "source": [
        "m2.addConstrs((        ?????????         ), name = 'xy_link')\n",
        "m2.update()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "H6xt_q-oPpRs"
      },
      "source": [
        " ### Using binary decision variables to open production facilities\n",
        "\n",
        " While we have linked distribution to production, we haven't implemented anything that restricts the `m2` optimal solution, so let's do that. Here are the three ideas specific to this problem that we'll dive into:\n",
        " - Regionality restrictions\n",
        " - Minimal number of facilities and strict limits\n",
        " - Maximize the minimum shipment"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lzN88XJpNQF1"
      },
      "source": [
        "### Regional restrictions\n",
        "One can come up with a number of reasons why a company making widgets, or anything, would like to implement some restrictions on where items are produced and distributed. For example, our model doesn't consider things like workforce in an area or getting raw materials. Each of these can have big impacts on decision making.\n",
        "\n",
        "The leadership at *Brigitte's Widgets* (yes, I finally named this company...I feel like they earned it) wrote in an email that we are to make sure that if the production facility in *Charleston* is open then *Cleveland* cannot be open and *Baltimore* cannot be open.\n",
        "\n",
        "Using $y_{city \\space name}$ , we can write this statement as:\n",
        "$$\n",
        "\\texttt{If}\\space y_{Charelston} = 1 \\texttt{, then}\\space y_{Cleveland}=0  \\space \\texttt{and}\\space y_{Baltimore} = 0\n",
        "$$\n",
        "\n",
        "Given what we have learned about how to use indicator constraints, this seems like the best approach. What goes to the left of the `>>` symbol is clear, but what should the right side look like?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vRkXsunCVAFG"
      },
      "source": [
        "##### What constraint is needed?\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZDfB6JaaGSM-"
      },
      "source": [
        "$$\n",
        "\\texttt{If}\\space\n",
        "y_{Charleston} = 1\\space \\texttt{, then}\\space y_{Cleveland} + y_{Baltimore} = 0\n",
        "$$"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "xVxSjLqwJayN",
        "outputId": "b6b770dc-d09f-4e86-cfaa-f7119ca42827"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Warning: variable name \"prod_ship[Baltimore,St. Louis]\" has a space\n",
            "Warning: constraint name \"can_produce[Little Rock]\" has a space\n",
            "Warning: to let Gurobi read it back, use rlp format\n"
          ]
        }
      ],
      "source": [
        "# regionality conditions\n",
        "reg_cond = m2.addConstr(         ?????????????               )\n",
        "m2.write('reg_cond.lp')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-qfh3PkvKCHW"
      },
      "source": [
        "##### It's always a good idea to write the `.lp` file. It's a great way to see if your model has any obvious errors.\n",
        "\n",
        "Now let's solve and look at the solution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 991
        },
        "id": "pRXJ1bK5KQMI",
        "outputId": "dd62393a-4af3-49b4-a78a-8e2f7f258c1e"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 41 rows, 35 columns and 120 nonzeros\n",
            "Model fingerprint: 0x53227d34\n",
            "Model has 1 general constraint\n",
            "Variable types: 30 continuous, 5 integer (5 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 2e+02]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [9e+01, 2e+02]\n",
            "  GenCon coe range [1e+00, 1e+00]\n",
            "Presolve removed 12 rows and 4 columns\n",
            "Presolve time: 0.00s\n",
            "Presolved: 29 rows, 31 columns, 99 nonzeros\n",
            "Variable types: 30 continuous, 1 integer (1 binary)\n",
            "Found heuristic solution: objective 1878.9800000\n",
            "\n",
            "Root relaxation: cutoff, 18 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "     0     0     cutoff    0      1878.98000 1878.98000  0.00%     -    0s\n",
            "\n",
            "Explored 1 nodes (18 simplex iterations) in 0.02 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 1: 1878.98 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 1.878980000000e+03, best bound 1.878980000000e+03, gap 0.0000%\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "\n",
              "  <div id=\"df-aafd5569-4891-4616-9697-dcb78c1dd2f1\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "      <th>cost</th>\n",
              "      <th>shipment</th>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>production</th>\n",
              "      <th>distribution</th>\n",
              "      <th></th>\n",
              "      <th></th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Baltimore</th>\n",
              "      <th>Lexington</th>\n",
              "      <td>4.33</td>\n",
              "      <td>92.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Richmond</th>\n",
              "      <td>1.96</td>\n",
              "      <td>116.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"4\" valign=\"top\">Cleveland</th>\n",
              "      <th>Columbia</th>\n",
              "      <td>2.43</td>\n",
              "      <td>89.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Indianapolis</th>\n",
              "      <td>2.37</td>\n",
              "      <td>95.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Lexington</th>\n",
              "      <td>2.54</td>\n",
              "      <td>29.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.88</td>\n",
              "      <td>12.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>Little Rock</th>\n",
              "      <th>St. Louis</th>\n",
              "      <td>2.92</td>\n",
              "      <td>140.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th rowspan=\"2\" valign=\"top\">Birmingham</th>\n",
              "      <th>Nashville</th>\n",
              "      <td>1.53</td>\n",
              "      <td>101.0</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>St. Louis</th>\n",
              "      <td>4.01</td>\n",
              "      <td>29.0</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-aafd5569-4891-4616-9697-dcb78c1dd2f1')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-aafd5569-4891-4616-9697-dcb78c1dd2f1 button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-aafd5569-4891-4616-9697-dcb78c1dd2f1');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-a997f2a7-b344-40ee-87ce-e7393e3745d1\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-a997f2a7-b344-40ee-87ce-e7393e3745d1')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-a997f2a7-b344-40ee-87ce-e7393e3745d1 button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ],
            "text/plain": [
              "                          cost  shipment\n",
              "production  distribution                \n",
              "Baltimore   Lexington     4.33      92.0\n",
              "            Richmond      1.96     116.0\n",
              "Cleveland   Columbia      2.43      89.0\n",
              "            Indianapolis  2.37      95.0\n",
              "            Lexington     2.54      29.0\n",
              "            St. Louis     4.88      12.0\n",
              "Little Rock St. Louis     2.92     140.0\n",
              "Birmingham  Nashville     1.53     101.0\n",
              "            St. Louis     4.01      29.0"
            ]
          },
          "execution_count": 20,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "m2.optimize()\n",
        "x_values = pd.Series(m2.getAttr('X', x), name = \"shipment\", index = transp_cost.index)\n",
        "sol2 = pd.concat([transp_cost, x_values], axis=1)\n",
        "sol2[sol2.shipment > 0]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QEKUoQJcKiNW"
      },
      "outputs": [],
      "source": [
        "# remove the regional condition constraint from the model\n",
        "m2.remove(reg_cond)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ovDMG-D05Kab"
      },
      "source": [
        "### Minimal number of facilities\n",
        "Given we have our link between distribution variables $x$ and production variables $p$ it's quite simple to see the minimal number of facilities needed to meet demand by setting the objective to minimize the sum over $y$.\n",
        "\n",
        "$$\n",
        "{\\rm min} \\sum_p y_p\n",
        "$$"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-APLxjAJ5IEG",
        "outputId": "0d028f6c-64de-46db-9a9e-2bca400a09e5"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 41 rows, 35 columns and 120 nonzeros\n",
            "Model fingerprint: 0x31d55d44\n",
            "Variable types: 30 continuous, 5 integer (5 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 2e+02]\n",
            "  Objective range  [1e+00, 1e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [9e+01, 2e+02]\n",
            "\n",
            "MIP start from previous solve produced solution with objective 4 (0.01s)\n",
            "Loaded MIP start from previous solve with objective 4\n",
            "\n",
            "Presolve removed 2 rows and 0 columns\n",
            "Presolve time: 0.00s\n",
            "Presolved: 39 rows, 35 columns, 121 nonzeros\n",
            "Variable types: 30 continuous, 5 integer (5 binary)\n",
            "\n",
            "Root relaxation: cutoff, 22 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "     0     0     cutoff    0         4.00000    4.00000  0.00%     -    0s\n",
            "\n",
            "Explored 1 nodes (22 simplex iterations) in 0.03 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 1: 4 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 4.000000000000e+00, best bound 4.000000000000e+00, gap 0.0000%\n"
          ]
        }
      ],
      "source": [
        "m2.setObjective(y.sum())\n",
        "m2.optimize()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Sc1l4q_Q-PtI"
      },
      "source": [
        "In order to meet demand, we need at least four production facilities. Let's compare the total cost to the baseline for this scenario."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "fuV2ZhFw7T8M",
        "outputId": "ef8a5eb5-9f23-49b5-82c6-b3cad020b9d2"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "152.55999999999995"
            ]
          },
          "execution_count": 23,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# (new objective value) - (previous objecive value)\n",
        "total_cost.getValue() - sum(sol2.cost*sol2.shipment)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Hkx0NlSe-oEz"
      },
      "source": [
        "Does this really say anything (for certain) about the cost of using only four facilities?\n",
        "\n",
        "No, since we didn't specify any way to prioritize between them there could be more than one set of four that can meet demand with different costs. Let's set the number of **production facilities to be at most four** and then minimize costs like we were doing before."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "QKmNte1E_u-A",
        "outputId": "e22a2f2e-700b-4864-f687-a210e280fa1d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 42 rows, 35 columns and 125 nonzeros\n",
            "Model fingerprint: 0xd0a037fc\n",
            "Variable types: 30 continuous, 5 integer (5 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 2e+02]\n",
            "  Objective range  [2e+00, 7e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [4e+00, 2e+02]\n",
            "\n",
            "MIP start from previous solve produced solution with objective 1878.98 (0.01s)\n",
            "Loaded MIP start from previous solve with objective 1878.98\n",
            "\n",
            "Presolve removed 2 rows and 0 columns\n",
            "Presolve time: 0.00s\n",
            "Presolved: 40 rows, 35 columns, 126 nonzeros\n",
            "Variable types: 30 continuous, 5 integer (5 binary)\n",
            "\n",
            "Root relaxation: objective 1.662450e+03, 18 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "*    0     0               0    1662.4500000 1662.45000  0.00%     -    0s\n",
            "\n",
            "Explored 1 nodes (18 simplex iterations) in 0.02 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 2: 1662.45 1878.98 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 1.662450000000e+03, best bound 1.662450000000e+03, gap 0.0000%\n"
          ]
        }
      ],
      "source": [
        "only_four = m2.??????????\n",
        "m2.setObjective(total_cost, GRB.MINIMIZE)\n",
        "m2.optimize()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LM6cXqnVAKu-"
      },
      "source": [
        "Now let's compare again the total costs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "mdIHyl27ARWC",
        "outputId": "ab1fa773-322a-45a2-cd4d-f39c3a83f108"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "-216.53"
            ]
          },
          "execution_count": 25,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "round(total_cost.getValue() - sum(sol2.cost*sol2.shipment),2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "l5_hFO-vAgMh"
      },
      "source": [
        "The cost only increases by a relatively small amount even though we are only using four facilities. The lesson here is only infer what your objective and constraints allow.\n",
        "\n",
        "This seems like we stumbled upon a handy way to **handle multiple objectives**. I wonder if we'll talk more about this in later sessions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OkShRiErBZ1o"
      },
      "outputs": [],
      "source": [
        "#remove the contraing limiting to four facilities\n",
        "m2.remove(only_four)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4FYCDv4zK3kr"
      },
      "source": [
        "### Maximize the minimum number of widgets shipped\n",
        "There could be a priority to make sure *Brigitte's Widgets* transports as much as possible per shipment. One way to interpret this is to maximize the minimum shipment, which means to increase the smallest $x_{p,d}$ as much as possible.\n",
        "\n",
        "Let's set $r = {\\rm min}_{p\\in P, d\\in D}\\{x_{p,d}\\}$ and then define the objective to be\n",
        "${\\rm max}\\space r$\n",
        "\n",
        "Let's use general constraints to model this."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-2OXBo9yNHjI",
        "outputId": "6c7b25e3-14b5-42c0-a3a2-c15d99d73125"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 41 rows, 36 columns and 120 nonzeros\n",
            "Model fingerprint: 0x26f08371\n",
            "Model has 1 general constraint\n",
            "Variable types: 30 continuous, 6 integer (5 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 2e+02]\n",
            "  Objective range  [1e+00, 1e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [9e+01, 2e+02]\n",
            "\n",
            "MIP start from previous solve produced solution with objective -0 (0.01s)\n",
            "Loaded MIP start from previous solve with objective -0\n",
            "\n",
            "Presolve added 1 rows and 25 columns\n",
            "Presolve time: 0.00s\n",
            "Presolved: 42 rows, 61 columns, 161 nonzeros\n",
            "Variable types: 30 continuous, 31 integer (30 binary)\n",
            "\n",
            "Root relaxation: objective 2.100000e+01, 17 iterations, 0.00 seconds (0.00 work units)\n",
            "\n",
            "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
            " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
            "\n",
            "*    0     0               0      21.0000000   21.00000  0.00%     -    0s\n",
            "\n",
            "Explored 1 nodes (17 simplex iterations) in 0.02 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 2: 21 -0 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 2.100000000000e+01, best bound 2.100000000000e+01, gap 0.0000%\n"
          ]
        }
      ],
      "source": [
        "r = m2.addVar(vtype=GRB.INTEGER, name = 'r')\n",
        "#minconstr = m2.addConstr(r == gp.min_([x[p,d] for p in production for d in distribution]), name=\"minconstr\")\n",
        "minconstr = m2.addGenConstrMin(r, [x[p,d] for p in production for d in distribution],name= \"minconstr\")\n",
        "m2.setObjective(r, GRB.MAXIMIZE)\n",
        "m2.optimize()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NXKlS87cYzUh"
      },
      "outputs": [],
      "source": [
        "m2.remove(minconstr)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Nu1g_ZsVWs5m"
      },
      "source": [
        "##### In this case, we don't need the `min` general constraint. We can get away with\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-kDcXfVDGqc3"
      },
      "source": [
        "$$\n",
        "r\\le x_{p,d}, \\quad ∀p \\in P, d \\in D.\n",
        "$$"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "EP36RjULcDP4",
        "outputId": "34473e9d-7be5-4919-8982-aacbc6462266"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n",
            "\n",
            "CPU model: AMD EPYC 7B12, instruction set [SSE2|AVX|AVX2]\n",
            "Thread count: 1 physical cores, 2 logical processors, using up to 2 threads\n",
            "\n",
            "Optimize a model with 71 rows, 36 columns and 180 nonzeros\n",
            "Model fingerprint: 0xbf3bad03\n",
            "Variable types: 30 continuous, 6 integer (5 binary)\n",
            "Coefficient statistics:\n",
            "  Matrix range     [1e+00, 2e+02]\n",
            "  Objective range  [1e+00, 1e+00]\n",
            "  Bounds range     [1e+00, 1e+00]\n",
            "  RHS range        [9e+01, 2e+02]\n",
            "\n",
            "MIP start from previous solve produced solution with objective 21 (0.01s)\n",
            "Loaded MIP start from previous solve with objective 21\n",
            "\n",
            "Presolve removed 30 rows and 5 columns\n",
            "Presolve time: 0.00s\n",
            "Presolved: 41 rows, 31 columns, 120 nonzeros\n",
            "Variable types: 30 continuous, 1 integer (0 binary)\n",
            "\n",
            "Explored 0 nodes (0 simplex iterations) in 0.01 seconds (0.00 work units)\n",
            "Thread count was 2 (of 2 available processors)\n",
            "\n",
            "Solution count 1: 21 \n",
            "\n",
            "Optimal solution found (tolerance 1.00e-04)\n",
            "Best objective 2.100000000000e+01, best bound 2.100000000000e+01, gap 0.0000%\n"
          ]
        }
      ],
      "source": [
        "m2.addConstrs(r <= x[p,d] for p in production for d in distribution)\n",
        "m2.optimize()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iwdxZlv6fDsN"
      },
      "source": [
        "Since we are maximizing $r$, we don't need to worry about specifying what bounds $r$ from below and the contraints we added are enough to model this relationship in this case.\n",
        "\n",
        "### On to the next session!\n",
        "It's time for a short break and for you to put your knowlege to the test!"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "oSK9Ezc_Ich8",
        "cF8C75m1Idh3",
        "HmLXki8LNBFp",
        "Kxjb1kiOM5A9",
        "Nl7_TcleJrPE",
        "vRkXsunCVAFG"
      ],
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
